/*****************************************************************************
*
*  PROJECT:     Native Executive
*  LICENSE:     See LICENSE in the top level directory
*  FILE:        NativeExecutive/CExecutiveManager.rwlock.impl.cpp
*  PURPOSE:     Read/Write lock internal implementation main
*  DEVELOPERS:  Martin Turski <quiret@gmx.de>
*
*  Multi Theft Auto is available from http://www.multitheftauto.com/
*
*****************************************************************************/

#include "StdInc.h"

#include <shared_mutex>

BEGIN_NATIVE_EXECUTIVE

// Actual implementation of CReadWriteLock.
struct CReadWriteLockNativeSRW
{
    inline CReadWriteLockNativeSRW( readWriteLockEnv_t *lockEnv )
    {
#ifdef _WIN32
        // We just have to initialize stuff once.
        lockEnv->InitializeSRWLock( &_nativeSRW );
#endif //_WIN32
    }

    inline ~CReadWriteLockNativeSRW( void )
    {
        return;
    }

    inline void EnterCriticalReadRegionNative( readWriteLockEnv_t *lockEnv )
    {
#ifdef _WIN32
        lockEnv->AcquireSRWLockShared( &_nativeSRW );
#else
#error Missing implementation
#endif //_WIN32
    }

    inline void LeaveCriticalReadRegionNative( readWriteLockEnv_t *lockEnv )
    {
#ifdef _WIN32
        lockEnv->ReleaseSRWLockShared( &_nativeSRW );
#else
#error Missing implementation
#endif //_WIN32
    }

    inline void EnterCriticalWriteRegionNative( readWriteLockEnv_t *lockEnv )
    {
#ifdef _WIN32
        lockEnv->AcquireSRWLockExclusive( &_nativeSRW );
#else
#error Missing implementation
#endif //_WIN32
    }

    inline void LeaveCriticalWriteRegionNative( readWriteLockEnv_t *lockEnv )
    {
#ifdef _WIN32
        lockEnv->ReleaseSRWLockExclusive( &_nativeSRW );
#else
#error Missing implementation
#endif //_WIN32
    }

    inline bool TryEnterCriticalReadRegionNative( readWriteLockEnv_t *lockEnv )
    {
#ifdef _WIN32
        return ( lockEnv->TryAcquireSRWLockShared( &_nativeSRW ) == TRUE );
#else
#error Missing implementation
#endif //_WIN32
    }

    inline bool TryEnterCriticalWriteRegionNative( readWriteLockEnv_t *lockEnv )
    {
#ifdef _WIN32
        return ( lockEnv->TryAcquireSRWLockExclusive( &_nativeSRW ) == TRUE );
#else
#error Missing implementation
#endif //_WIN32
    }

#ifdef _WIN32
    SRWLOCK _nativeSRW;
#endif //_WIN32
};

struct CReadWriteLockNativeSTL
{
    AINLINE CReadWriteLockNativeSTL( void )
    {
        return;
    }

    AINLINE void enter_read( readWriteLockEnv_t *lockEnv )
    {
        this->mutex.lock_shared();
    }

    AINLINE void leave_read( readWriteLockEnv_t *lockEnv )
    {
        this->mutex.unlock_shared();
    }

    AINLINE void enter_write( readWriteLockEnv_t *lockEnv )
    {
        this->mutex.lock();
    }

    AINLINE void leave_write( readWriteLockEnv_t *lockEnv )
    {
        this->mutex.unlock();
    }

    AINLINE bool try_enter_read( readWriteLockEnv_t *lockEnv )
    {
        return this->mutex.try_lock_shared();
    }

    AINLINE bool try_enter_write( readWriteLockEnv_t *lockEnv )
    {
        return this->mutex.try_lock();
    }

    std::shared_timed_mutex mutex;
};

#if 0

// Actual implementation of CReentrantReadWriteLock
class exclusive_lock
{
public:
    inline exclusive_lock( void )
#ifdef _WIN32
        : srwLock( SRWLOCK_INIT )
#else
        : lockIsTaken( false )
#endif
    {
        return;
    }

    inline void enter( void )
    {
#ifdef _WIN32
        AcquireSRWLockExclusive( &srwLock );
#else
        while ( true )
        {
            bool wantToTakeOrWasTaken = true;

            bool hasTaken = this->lockIsTaken.compare_exchange_strong( wantToTakeOrWasTaken, true );

            if ( hasTaken )
            {
                break;
            }

            std::unique_lock <std::mutex> ul_isLocked( this->mutex_isLocked );

            isLocked.wait( ul_isLocked );
        }
#endif
    }

    inline void leave( void )
    {
#ifdef _WIN32
        ReleaseSRWLockExclusive( &srwLock );
#else
        this->lockIsTaken.store( false );

        isLocked.notify_one();
#endif
    }

private:
#ifdef _WIN32
    SRWLOCK srwLock;
#else
    std::mutex mutex_isLocked;
    std::condition_variable isLocked;
    std::atomic <bool> lockIsTaken;
#endif
};

class ctxlock_reentrant
{
public:
    typedef void* context_t;
    
    inline ctxlock_reentrant( void )
    {
        this->contextCount = 0;
        this->currentContext = NULL;    // NULL could be used by the runtime as shared context.
    }

private:
    inline bool IsContextTaken( void *ctxVal )
    {
        return ( this->contextCount != 0 && this->currentContext != ctxVal );
    }
    
public:
    inline void enter( context_t ctxVal )
    {
        while ( true )
        {
            bool couldBeFree = ( IsContextTaken( ctxVal ) == false );

            if ( couldBeFree )
            {
                mutexContextCritical.enter();

                bool isActuallyFree = ( IsContextTaken( ctxVal ) == false );

                if ( isActuallyFree )
                {
                    break;
                }

                mutexContextCritical.leave();
            }

            continue;
        }

        this->contextCount++;
        this->currentContext = ctxVal;

        mutexContextCritical.leave();
    }
    
    inline void leave( void )
    {
        // We can do that safely, as it is an atomic variable.
        this->contextCount--;
    }

private:
    exclusive_lock mutexContextCritical;

    std::atomic <unsigned long> contextCount;
    volatile context_t currentContext;
};

class rwlock_reentrant_bythread
{
    ctxlock_reentrant ctxLock;
public:
    inline void enter_write( void )
    {
        ctxLock.enter( (ctxlock_reentrant::context_t)std::hash <std::thread::id> () ( std::this_thread::get_id() ) );
    }

    inline void leave_write( void )
    {
        ctxLock.leave();
    }

    inline void enter_read( void )
    {
        ctxLock.enter( NULL );
    }

    inline void leave_read( void )
    {
        ctxLock.leave();
    }
};

struct CReentrantReadWriteLockNativeSpecial
{
    inline CReentrantReadWriteLockNativeSpecial( void )
    {
        return;
    }

    inline ~CReentrantReadWriteLockNativeSpecial( void )
    {
        return;
    }

    inline void EnterCriticalReadRegionNative( void )
    {
        this->ctxLock.enter_read();
    }

    inline void LeaveCriticalReadRegionNative( void )
    {
        this->ctxLock.leave_read();
    }

    inline void EnterCriticalWriteRegionNative( void )
    {
        this->ctxLock.enter_write();
    }

    inline void LeaveCriticalWriteRegionNative( void )
    {
        this->ctxLock.leave_write();
    }

    rwlock_reentrant_bythread ctxLock;
};

#endif //0

// Constructors and destructors for locks.
void nativesrw_constructor( void *mem, readWriteLockEnv_t *lockEnv )
{
    new (mem) CReadWriteLockNativeSRW( lockEnv );
}

void nativesrw_destructor( void *mem, readWriteLockEnv_t *lockEnv )
{
    ( (CReadWriteLockNativeSRW*)mem )->~CReadWriteLockNativeSRW();
}

void stlrw_constructor( void *mem, readWriteLockEnv_t *lockEnv )
{
    new (mem) CReadWriteLockNativeSTL();
}

void stlrw_destructor( void *mem, readWriteLockEnv_t *lockEnv )
{
    ( (CReadWriteLockNativeSTL*)mem )->~CReadWriteLockNativeSTL();
}

template <typename anonFuncType, typename membFuncPtrType>
static AINLINE anonFuncType to_anon_function( membFuncPtrType ptr )
{
    union
    {
        membFuncPtrType ptrStore;
        anonFuncType vptr;
    };

    ptrStore = ptr;

    return vptr;
}

// Lock usage decision.
void readWriteLockEnv_t::Initialize( CExecutiveManagerNative *execMan )
{
    kernel32_module = LoadLibraryW( L"Kernel32.dll" );

    if ( kernel32_module )
    {
        METHODDECL_FETCH( kernel32_module, InitializeSRWLock );
        METHODDECL_FETCH( kernel32_module, AcquireSRWLockShared );
        METHODDECL_FETCH( kernel32_module, ReleaseSRWLockShared );
        METHODDECL_FETCH( kernel32_module, AcquireSRWLockExclusive );
        METHODDECL_FETCH( kernel32_module, ReleaseSRWLockExclusive );
        METHODDECL_FETCH( kernel32_module, TryAcquireSRWLockShared );
        METHODDECL_FETCH( kernel32_module, TryAcquireSRWLockExclusive );
        
        if ( InitializeSRWLock &&
             AcquireSRWLockShared && ReleaseSRWLockShared &&
             AcquireSRWLockExclusive && ReleaseSRWLockExclusive &&
             TryAcquireSRWLockShared && TryAcquireSRWLockExclusive )
        {
            // Enable SRW lock.
            this->readWriteLockSize = sizeof(CReadWriteLockNativeSRW);

            this->constructor = nativesrw_constructor;
            this->destructor = nativesrw_destructor;
            this->enter_read =
                to_anon_function <rwlockimpl_enter_read> ( &CReadWriteLockNativeSRW::EnterCriticalReadRegionNative );
            this->leave_read = 
                to_anon_function <rwlockimpl_leave_read> ( &CReadWriteLockNativeSRW::LeaveCriticalReadRegionNative );
            this->enter_write =
                to_anon_function <rwlockimpl_enter_write> ( &CReadWriteLockNativeSRW::EnterCriticalWriteRegionNative );
            this->leave_write =
                to_anon_function <rwlockimpl_leave_write> ( &CReadWriteLockNativeSRW::LeaveCriticalWriteRegionNative );
            this->try_enter_read =
                to_anon_function <rwlockimpl_try_enter_read> ( &CReadWriteLockNativeSRW::TryEnterCriticalReadRegionNative );
            this->try_enter_write =
                to_anon_function <rwlockimpl_try_enter_write> ( &CReadWriteLockNativeSRW::TryEnterCriticalWriteRegionNative );
        }
    }

    // Default to STL.
    if ( !this->constructor )
    {
        this->readWriteLockSize = sizeof(CReadWriteLockNativeSTL);

        this->constructor = stlrw_constructor;
        this->destructor = stlrw_destructor;

        this->enter_read =
            to_anon_function <rwlockimpl_enter_read> ( &CReadWriteLockNativeSTL::enter_read );
        this->leave_read =
            to_anon_function <rwlockimpl_leave_read> ( &CReadWriteLockNativeSTL::leave_read );
        this->enter_write =
            to_anon_function <rwlockimpl_enter_write> ( &CReadWriteLockNativeSTL::enter_write );
        this->leave_write =
            to_anon_function <rwlockimpl_leave_write> ( &CReadWriteLockNativeSTL::leave_write );
        this->try_enter_read =
            to_anon_function <rwlockimpl_try_enter_read> ( &CReadWriteLockNativeSTL::try_enter_read );
        this->try_enter_write =
            to_anon_function <rwlockimpl_try_enter_write> ( &CReadWriteLockNativeSTL::try_enter_write );
    }
}

void readWriteLockEnv_t::Shutdown( CExecutiveManagerNative *execMan )
{
    if ( kernel32_module )
    {
        FreeLibrary( kernel32_module );

        kernel32_module = NULL;
    }
}

END_NATIVE_EXECUTIVE